#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>

#include <common/macro/build.h>
#include <common/charreader.h>
#include <common/io.h>
#include <common/util.h>
#include <common/macro/array.h>
#include <table/column.h>

API_PUBLIC column_t *column_new_list(char *name, const char *datatype_name, char **args, int argc) {
	int i;
	column_t *column;
	datatype_t *datatype;

	assert(name != NULL);
	assert(datatype_name != NULL);

	datatype = datatype_lookup(datatype_name);
	if (datatype == NULL) {
		return NULL;
	}

	column = malloc(sizeof(*column));

	column->name = strdup(name);
	column->datatype = datatype;

	column->dt_args = NULL;
	column->dt_args_raw = malloc(sizeof(*column->dt_args_raw) * argc);
	column->dt_args_raw_c = 0;
	for (i = 0; i < argc; i++) {
		ARRAY_SET(column->dt_args_raw, column->dt_args_raw_c, strdup(args[i]));
		column->dt_args_raw_c++;
	}

	return column;
}

API_PUBLIC column_t *column_new(char *name, const char *datatype_name, ...) {
	va_list ap;
	char *str;
	column_t *column;
	datatype_t *datatype;

	assert(name != NULL);
	assert(datatype_name != NULL);

	datatype = datatype_lookup(datatype_name);
	if (datatype == NULL) {
		return NULL;
	}

	column = malloc(sizeof(*column));

	column->name = strdup(name);
	column->datatype = datatype;

	column->dt_args = NULL;
	column->dt_args_raw = NULL;
	column->dt_args_raw_c = 0;

	va_start(ap, datatype_name);
	while ((str = va_arg(ap, char*)) != NULL) {
		ARRAY_APPEND(column->dt_args_raw, column->dt_args_raw_c, strdup(str));
	}
	va_end(ap);

	return column;
}

API_PUBLIC void column_destroy(column_t *column) {
	int i;

	if (column == NULL) { return; }

	if (column->dt_args_raw != NULL) {
		for (i = 0; i < column->dt_args_raw_c; i++)
			free(column->dt_args_raw[i]);
		free(column->dt_args_raw);
	}

	/* Ditch compiled args, too. */
	if (column->datatype->args_destroy != NULL)
		column->datatype->args_destroy(column->dt_args);

	free(column->name);
	free(column);
}

API_LIBLOCAL void column_compile_args(column_t *column) {
	assert(column != NULL);
	
	if (column->datatype->args_parse != NULL)
		column->dt_args = column->datatype->args_parse(column->dt_args_raw, column->dt_args_raw_c);
}

API_LIBLOCAL int column_serialize_file(column_t *column, iohandle_t out) {
	uint16_t i, items;

	assert(column != NULL);
	assert(out != NULL);

	/* Column name. */
	if (io_write(column->name, strlen(column->name) + 1, 1, out) != 1) {
		return -1;
	}

	/* Data type name. */
	if (io_write(column->datatype->name, strlen(column->datatype->name) + 1, 1, out) != 1) {
		return -1;
	}

	/* Raw arguments: */
	/*   - argument count */
	items = (uint16_t)column->dt_args_raw_c;
	if (io_write(&items, sizeof(items), 1, out) != 1) {
		return -1;
	}

	/*   - arguments themselves */
	for (i = 0; i < items; i++) {
		if (io_write(column->dt_args_raw[i], strlen(column->dt_args_raw[i]) + 1, 1, out) != 1) {
			return -1;
		}
	}

	return 0;
}

API_LIBLOCAL column_t *column_deserialize_file(iohandle_t in) {
	char *column_name;
	int c;
	charreader_t *charreader;
	column_t *column;
	uint16_t i, items;

	assert(in != NULL);

	charreader = charreader_new();

	/* Column name. */
	do {
		c = fgetc(in);
		if (c < 0) {
			charreader_destroy(charreader);
			return NULL;
		}
		charreader_pushchar(charreader, c);
	} while (c != '\0');
	column_name = strdup(charreader_getstring(charreader));
	charreader_flush(charreader);

	/* Data type name. */
	do {
		c = fgetc(in);
		if (c < 0) {
			charreader_destroy(charreader);
			free(column_name);
			return NULL;
		}
		charreader_pushchar(charreader, c);
	} while (c != '\0');
	column = column_new(column_name, charreader_getstring(charreader), NULL);
	free(column_name);
	charreader_flush(charreader);

	if (column == NULL) {
		return NULL;
	}

	/* Raw arguments: */
	/*   - argument count */
	if (io_read(&items, sizeof(items), 1, in) != 1) {
		column_destroy(column);
		charreader_destroy(charreader);
		return NULL;
	}

	/*   - arguments themselves */
	for (i = 0; i < items; i++) {
		do {
			c = fgetc(in);
			if (c < 0) {
				column_destroy(column);
				charreader_destroy(charreader);
				return NULL;
			}
			charreader_pushchar(charreader, c);
		} while (c != '\0');

		ARRAY_APPEND(column->dt_args_raw, column->dt_args_raw_c, strdup(charreader_getstring(charreader)));
	}
	
	/* Compile the retrieved arguments. */
	column_compile_args(column);

	charreader_destroy(charreader);

	return column;
}

API_LIBLOCAL ssize_t column_serialize(column_t *column, char *out, ssize_t out_size) {
	ssize_t offset, itemlen;
	uint16_t i, items;

	assert(column != NULL);
	assert(out != NULL);

	offset = 0;

	/* Column name. */
	itemlen = strlen(column->name) + 1;
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], column->name, itemlen);
	offset += itemlen;

	/* Data type name. */
	itemlen = strlen(column->datatype->name) + 1;
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], column->datatype->name, itemlen);
	offset += itemlen;

	/* Raw arguments: */
	/*   - argument count */
	items = (uint16_t)column->dt_args_raw_c;
	itemlen = sizeof(items);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &items, itemlen);
	offset += itemlen;

	/*   - arguments themselves */
	for (i = 0; i < items; i++) {
		itemlen = strlen(column->dt_args_raw[i]) + 1;
		if (itemlen > out_size - offset) { return -1; }
		memcpy(&out[offset], column->dt_args_raw[i], itemlen);
		offset += itemlen;
	}

	return offset;
}

API_LIBLOCAL ssize_t column_deserialize(column_t **column, const char *in, ssize_t in_size) {
	char *column_name, *datatype_name, *data;
	datatype_t *datatype;
	ssize_t offset, itemlen;
	uint16_t i, items;

	assert(in != NULL);

	offset = 0;

	/* Column name. */
	itemlen = util_strnlen(&in[offset],  in_size);
	if (itemlen > in_size - offset) { return -1; }
	column_name = malloc(itemlen + 1);
	memcpy(column_name, &in[offset], itemlen + 1);
	column_name[itemlen] = '\0';
	offset += itemlen + 1;

	/* Data type name. */
	itemlen = util_strnlen(&in[offset],  in_size);
	if (itemlen > in_size - offset) { free(column_name); return -1; }
	datatype_name = malloc(itemlen + 1);
	memcpy(column_name, &in[offset], itemlen + 1);
	datatype_name[itemlen] = '\0';
	offset += itemlen + 1;

	/* Using the name, identify the installed datatype. */
	datatype = datatype_lookup(datatype_name);
	if (datatype == NULL) { free(column_name); return -1; }

	*column = column_new(column_name, datatype_name, NULL);
	free(column_name);
	free(datatype_name);
	if ((*column) == NULL) { return -1; }

	/* Raw arguments: */
	/*   - argument count */
	itemlen = sizeof(items);
	if (itemlen > in_size - offset) { column_destroy(*column); return -1; }
	memcpy(&items, &in[offset], itemlen);
	offset += itemlen;

	/*   - arguments themselves */
	for (i = 0; i < items; i++) {
		itemlen = util_strnlen(&in[offset],  in_size);
		if (itemlen > in_size - offset) { return -1; }
		data = malloc(itemlen + 1);
		memcpy(data, &in[offset], itemlen + 1);
		data[itemlen] = '\0';
		offset += itemlen + 1;

		ARRAY_APPEND((*column)->dt_args_raw, (*column)->dt_args_raw_c, data);
	}
	
	/* Compile the retrieved arguments. */
	column_compile_args(*column);

	return offset;
}
